package ua.org.inspace.nosqlgui;

import ua.org.inspace.exception.NoSqlException;

import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import java.util.List;
import java.util.Properties;
import java.util.logging.*;

/**
 * Created by: Mykhailo Matiiasevych
 * Date: 31.10.11
 * Time: 16:39
 */
public class MainForm {
    private JTabbedPane tabbedPane1;
    private JPanel panel1;
    private JTextField kvhomePath;
    private JButton kvhomeSelect;
    private JTextField kvrootPath;
    private JButton kvrootSelect;
    private JTextField port;
    private JTextField adminPort;
    private JButton makeBootConfigButton;
    private JTextArea log;
    private JTextField haPortRange;
    private JTextField kvhost;
    private JButton ping;
    private JButton stop;
    private JButton restart;
    private JComboBox scriptsList;
    private JButton loadScriptButton;
    private JButton runScriptButton;
    private JButton refreshButton;
    private JTextArea scriptEditor;
    private JButton saveScriptButton;
    private JButton startJava;

    private String jar = "kvstore-1.1.100.jar";
    private String secfile = "security.policy";
    private String configile = "config.xml";
    private String scriptsPath = "scripts";
    private String SNAClassName = "oracle.kv.impl.sna.StorageNodeAgentImpl";
    private String PingClassName = "oracle.kv.util.Ping";
    private String AdminClassName = "oracle.kv.impl.admin.client.KVAdmin";
    ScriptUtils scriptUtils = new ScriptUtils();
    private String bundledScripts[] = new String[]{"localInstall.txt", "startAdmin.txt"};

    public static void main(String[] args) {

        LogManager.getLogManager().reset();

        JFrame frame = new JFrame("MainForm");
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
        final MainForm form = new MainForm();
        frame.setContentPane(form.panel1);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.addWindowListener(new WindowListener() {
            public void windowOpened(WindowEvent e) {
            }

            public void windowClosing(WindowEvent e) {
                saveSettings(form);
            }

            public void windowClosed(WindowEvent e) {

            }

            public void windowIconified(WindowEvent e) {
            }

            public void windowDeiconified(WindowEvent e) {
            }

            public void windowActivated(WindowEvent e) {
            }

            public void windowDeactivated(WindowEvent e) {
            }
        });

        frame.pack();
        frame.setVisible(true);

    }

    public MainForm() {
        registerLogger();

        loadSettings(this);
        refreshScriptsList();
        kvhomeSelect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                if (fileChooser.showOpenDialog(kvhomeSelect) == JFileChooser.APPROVE_OPTION) {
                    kvhomePath.setText(fileChooser.getSelectedFile().getAbsolutePath());
                }
            }
        });
        kvrootSelect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                if (fileChooser.showOpenDialog(kvhomeSelect) == JFileChooser.APPROVE_OPTION) {
                    kvrootPath.setText(fileChooser.getSelectedFile().getAbsolutePath());
                }
            }
        });
        makeBootConfigButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    requireRoot();
                    log("Entering makebootconfig, making files " + secfile + " and " + configile + " in");
                    log("\tdirectory " + kvrootPath.getText());
                    validatePorts();
                    addSecFile();
                    addConfigFile();
                    copyScripts();
                    refreshScriptsList();
                    copyLog();
                    log("Leaving function makebootconfig");
                } catch (NoSqlException ex) {
                    log(ex.getMessage());
                }

            }
        });

        startJava.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    log("Start server as java");
                    requireRoot();

                    final String args[] = new String[]{
                            "-root",
                            "\"" + kvrootPath.getText().trim() + "\"",
                            "-config",
                            "\"" + configile + "\""
                    };

                    runProcess(SNAClassName, args);
                } catch (NoSqlException ex) {
                    log(ex.getMessage());
                }
            }
        });

        ping.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                log("Entering function ping");
                try {
                    validatePort();
                    final String args[] = new String[]{
                            "-host",
                            "\"" + kvhost.getText().trim() + "\"",
                            "-port",
                            port.getText().trim()
                    };
                    runProcess(PingClassName, args);
                } catch (NoSqlException ex) {
                    log(ex.getMessage());
                }
            }
        });

        stop.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    log("Stop server");
                    requireRoot();

                    final String args[] = new String[]{
                            "-root",
                            "\"" + kvrootPath.getText().trim() + "\"",
                            "-config",
                            "\"" + configile + "\"",
                            "-shutdown"
                    };

                    runProcess(SNAClassName, args);

                } catch (NoSqlException ex) {
                    log(ex.getMessage());
                }
            }
        });
        restart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                stop.doClick();
                startJava.doClick();
            }
        });

        refreshButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                refreshScriptsList();
            }
        });
        loadScriptButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (scriptsList.getSelectedItem() != null) {
                    scriptEditor.setText("");
                    File script = (File) scriptsList.getSelectedItem();
                    scriptEditor.setText(scriptUtils.loadScript(script));
                }
            }
        });
        saveScriptButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setCurrentDirectory(new File(kvrootPath.getText() + "/" + scriptsPath));
                fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                if (fileChooser.showSaveDialog(saveScriptButton) == JFileChooser.APPROVE_OPTION) {
                    scriptUtils.saveScriptAs(fileChooser.getSelectedFile(), scriptEditor.getText());
                    refreshScriptsList();
                }

            }
        });
        runScriptButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    log("Run admin script");
                    validatePort();

                    File scriptFile = new File(kvhomePath.getText(), "runscript.txt");
                    scriptUtils.saveScriptAs(scriptFile, scriptEditor.getText());

                    String args[] = new String[]{
                            "-host",
                            "\"" + kvhost.getText().trim() + "\"",
                            "-port",
                            port.getText().trim(),
                            "load",
                            "\"" + scriptFile.getAbsolutePath() + "\""
                    };

                    runProcess(AdminClassName, args);
                } catch (NoSqlException ex) {
                    log(ex.getMessage());
                }
            }
        });

    }

    private void addConfigFile() throws NoSqlException {
        File conf = new File(kvrootPath.getText() + "/" + configile);
        try {
            if (!conf.createNewFile()) {
                log(configile + " exists, not creating");
            }

            FileWriter writer = new FileWriter(conf, false);
            writer.append("<config version=\"1\">\n")
                    .append("  <component name=\"bootstrapParams\" type=\"bootstrapParams\">\n")
                    .append("    <property name=\"hostname\" value=\"" + kvhost.getText().trim() + "\"/>\n")
                    .append("    <property name=\"registryPort\" value=\"" + port.getText().trim() + "\"/>\n")
                    .append("    <property name=\"adminHttpPort\" value=\"" + adminPort.getText().trim() + "\"/>\n")
                    .append("    <property name=\"haPortRange\" value=\"" + haPortRange.getText().trim() + "\"/>\n")
                    .append("  </component>\n")
                    .append("</config>");
            writer.close();
            log(configile + " saved");
        } catch (IOException ex) {
            throw new NoSqlException("Cant't create CONFIGFILE");
        }

    }

    private void addSecFile() throws NoSqlException {
        File secFile = new File(kvrootPath.getText() + "/" + secfile);
        try {
            if (!secFile.createNewFile()) {
                log(secfile + " exists, not creating");
            }
        } catch (IOException ex) {
            throw new NoSqlException("Cant't create SECFILE");
        }
    }

    private void copyScripts() throws NoSqlException {
        scriptUtils.copyScripts(kvrootPath.getText() + "/" + scriptsPath, bundledScripts);
    }

    private void copyLog() throws NoSqlException {
        InputStream in = MainForm.class.getResourceAsStream("/ua/org/inspace/nosqlgui/logging.properties");
        OutputStream out = null;
        try {
            out = new FileOutputStream(kvrootPath.getText() + "/logging.properties");
            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = in.read(buffer)) != -1) {
                out.write(buffer, 0, count);
            }
            out.flush();
        } catch (Exception ex) {
            throw new NoSqlException(ex);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void validateHaPortRange() throws NoSqlException {
        if (haPortRange.getText().trim().contains(" ")) {
            throw new NoSqlException("Invalid format for HaPortRange flag (no spaces allowed)");
        }
    }

    private void validatePort() throws NoSqlException {
        try {
            Long.parseLong(port.getText().trim());
        } catch (NumberFormatException ex) {
            throw new NoSqlException("Invalid format for PORT flag (must be numeric)");
        }
    }

    private void validateAdminPort() throws NoSqlException {
        try {
            Long.parseLong(adminPort.getText().trim());
        } catch (NumberFormatException ex) {
            throw new NoSqlException("Invalid format for ADMIN PORT flag (must be numeric)");
        }
    }


    private void validatePorts() throws NoSqlException {
        validateHaPortRange();
        validatePort();
        validateAdminPort();
    }

    private void requireRoot() throws NoSqlException {
        if (kvrootPath.getText() == null || kvrootPath.getText().equals("")) {
            throw new NoSqlException("KVROOT is empty");
        }
        File kvrootDir = new File(kvrootPath.getText());
        if (!kvrootDir.exists()) {
            if (!kvrootDir.mkdir()) {
                throw new NoSqlException("Cant't create KVROOT directory");
            }
        }
    }

    protected void log(String msg) {
        log.append(msg);
        if (!msg.endsWith("\n")) {
            log.append("\n");
        }
    }

    private static void saveSettings(MainForm form) {
        Properties props = new Properties();
        props.setProperty("kvhomePath", form.kvhomePath.getText().trim());
        props.setProperty("kvrootPath", form.kvrootPath.getText().trim());
        props.setProperty("port", form.port.getText().trim());
        props.setProperty("adminPort", form.adminPort.getText().trim());
        props.setProperty("haPortRange", form.haPortRange.getText().trim());
        props.setProperty("kvhost", form.kvhost.getText().trim());
        try {
            OutputStream out = new FileOutputStream("config.xml");
            props.storeToXML(out, "config.xml");
            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static void loadSettings(MainForm form) {
        Properties props = new Properties();
        try {
            InputStream in = new FileInputStream("config.xml");
            if (in.available() > 0) {
                props.loadFromXML(in);
                form.kvhomePath.setText(props.getProperty("kvhomePath"));
                form.kvrootPath.setText(props.getProperty("kvrootPath"));
                form.port.setText(props.getProperty("port"));
                form.adminPort.setText(props.getProperty("adminPort"));
                form.haPortRange.setText(props.getProperty("haPortRange"));
                form.kvhost.setText(props.getProperty("kvhost"));
            }
            in.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void refreshScriptsList() {
        scriptsList.removeAllItems();
        List<File> list = scriptUtils.getScriptsList(kvrootPath.getText() + "/" + scriptsPath);
        for (File script : list) {
            scriptsList.addItem(script);
        }
    }

    private void registerLogger() {
        Logger logger;
        LoggingOutputStream los;

        logger = Logger.getLogger("stdout");
        los = new LoggingOutputStream(logger, StdOutErrLevel.STDOUT);
        System.setOut(new PrintStream(los, true));
        logger.addHandler(new LoggerHandler(this, "info"));

        logger = Logger.getLogger("stderr");
        los = new LoggingOutputStream(logger, StdOutErrLevel.STDERR);
        System.setErr(new PrintStream(los, true));
        logger.addHandler(new LoggerHandler(this, "error"));
    }

    private String getJavaString(String className, String args[]) {

        String runString = "java -Djava.util.logging.config.file=" + kvrootPath.getText().trim() + "/logging.properties -cp \"" +
                kvhomePath.getText().trim() + "/lib/" + jar + "\" " + className + " ";
        for (String arg : args) {
            runString += arg + " ";
        }
        return runString;
    }

    private void runProcess(String className, String args[]) throws NoSqlException {
        String runString = getJavaString(className, args);
        log(runString);
        try {
            Process p = Runtime.getRuntime().exec(runString);
            LogStreamListener lsl = new LogStreamListener(p.getInputStream(), "stdout", StdOutErrLevel.STDOUT);
            (new Thread(lsl)).start();
            lsl = new LogStreamListener(p.getErrorStream(), "stderr", StdOutErrLevel.STDERR);
            (new Thread(lsl)).start();
        } catch (Exception ex) {
            throw new NoSqlException(ex);
        }
    }

}
